package com.nervenets.general.wapper;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.mapping.*;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.springframework.util.ReflectionUtils;

import javax.persistence.Column;
import javax.persistence.JoinColumn;
import java.lang.reflect.Field;
import java.util.Iterator;

/**
 * 实体对象与数据库之间的关系整合
 * 同步对象的注释到数据库字段注释
 */
public class HibernateCommentIntegrator implements Integrator {
    public static final HibernateCommentIntegrator INSTANCE = new HibernateCommentIntegrator();

    public HibernateCommentIntegrator() {
        super();
    }

    /**
     * Perform comment integration.
     *
     * @param metadata        The "compiled" representation of the mapping information
     * @param sessionFactory  The session factory being created
     * @param serviceRegistry The session factory's service registry
     */
    @Override
    public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
        processComment(metadata);
    }

    /**
     * Not used.
     *
     * @param sessionFactoryImplementor     The session factory being closed.
     * @param sessionFactoryServiceRegistry That session factory's service registry
     */
    @Override
    public void disintegrate(SessionFactoryImplementor sessionFactoryImplementor, SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {
    }

    /**
     * Process comment annotation.
     *
     * @param metadata process annotation of this {@code Metadata}.
     */
    private void processComment(Metadata metadata) {
        for (PersistentClass persistentClass : metadata.getEntityBindings()) {
            // Process the Comment annotation is applied to Class
            Class<?> clz = persistentClass.getMappedClass();
            if (clz.isAnnotationPresent(ApiModel.class)) {
                ApiModel comment = clz.getAnnotation(ApiModel.class);
                persistentClass.getTable().setComment(comment.value());
            }

            // Process Comment annotations of identifier.
            Property identifierProperty = persistentClass.getIdentifierProperty();
            if (identifierProperty != null) {
                fieldComment(persistentClass, identifierProperty.getName());
            } else {
                org.hibernate.mapping.Component component = persistentClass.getIdentifierMapper();
                if (component != null) {
                    //noinspection unchecked
                    Iterator<Property> iterator = component.getPropertyIterator();
                    while (iterator.hasNext()) {
                        fieldComment(persistentClass, iterator.next().getName());
                    }
                }
            }
            // Process fields with Comment annotation.
            //noinspection unchecked
            Iterator<Property> iterator = persistentClass.getPropertyIterator();
            while (iterator.hasNext()) {
                fieldComment(persistentClass, iterator.next().getName());
            }
        }
        for (Collection collectionBinding : metadata.getCollectionBindings()) {
            PersistentClass owner = collectionBinding.getOwner();
            Class<?> clz = owner.getMappedClass();
            String ownerName = null;
            if (clz.isAnnotationPresent(ApiModel.class)) {
                ApiModel comment = clz.getAnnotation(ApiModel.class);
                ownerName = comment.value();
                Iterator<Selectable> columnIterator = collectionBinding.getKey().getColumnIterator();
                while (columnIterator.hasNext()) {
                    Selectable selectable = columnIterator.next();
                    if (selectable instanceof org.hibernate.mapping.Column) {
                        org.hibernate.mapping.Column column = (org.hibernate.mapping.Column) selectable;
                        column.setComment(ownerName + "外键");
                    }
                }
            }

            Value element = collectionBinding.getElement();
            String elementName = null;
            if (element instanceof ManyToOne) {
                ManyToOne manyToOne = (ManyToOne) element;
                try {
                    clz = Class.forName(manyToOne.getReferencedEntityName());
                    if (clz.isAnnotationPresent(ApiModel.class)) {
                        ApiModel comment = clz.getAnnotation(ApiModel.class);
                        elementName = comment.value();
                        Iterator<Selectable> columnIterator = manyToOne.getColumnIterator();
                        while (columnIterator.hasNext()) {
                            Selectable selectable = columnIterator.next();
                            if (selectable instanceof org.hibernate.mapping.Column) {
                                org.hibernate.mapping.Column column = (org.hibernate.mapping.Column) selectable;
                                column.setComment(elementName + "外键");
                            }
                        }
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }

            if (null != ownerName && null != elementName) {
                Table table = collectionBinding.getCollectionTable();
                table.setComment(ownerName + "~" + elementName + "的关联表");
            }
        }
    }

    /**
     * Process @{code comment} annotation of field.
     *
     * @param persistentClass Hibernate {@code PersistentClass}
     * @param columnName      name of field
     */
    private void fieldComment(PersistentClass persistentClass, String columnName) {
        try {
            Field field = ReflectionUtils.findField(persistentClass.getMappedClass(), columnName);
            assert field != null;
            if (field.isAnnotationPresent(Column.class)) {
                String annotationName = field.getAnnotation(Column.class).name().trim();
                if (!annotationName.isEmpty()) {
                    columnName = annotationName;
                }
            } else if (field.isAnnotationPresent(JoinColumn.class)) {
                String annotationName = field.getAnnotation(JoinColumn.class).name().trim();
                if (!annotationName.isEmpty()) {
                    columnName = annotationName;
                }
            }
            if (field.isAnnotationPresent(ApiModelProperty.class)) {
                String comment = field.getAnnotation(ApiModelProperty.class).value();
                //noinspection unchecked
                Iterator<org.hibernate.mapping.Column> columnIterator = persistentClass.getTable().getColumnIterator();
                while (columnIterator.hasNext()) {
                    org.hibernate.mapping.Column column = columnIterator.next();
                    if (columnName.equalsIgnoreCase(column.getName()) || columnName.equalsIgnoreCase(column.getName().replace("_", ""))) {
                        column.setComment(comment);
                        break;
                    }
                }
            }
        } catch (Exception ignored) {
            ignored.printStackTrace();
        }
    }
}
